home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
HPAVC
/
HPAVC CD-ROM.iso
/
CIS_GAME.ARJ
/
QTIME1.THD
< prev
next >
Wrap
Text File
|
1993-07-01
|
14KB
|
274 lines
________________________ Subj: Resetting System Time ________________________
Fm: Mark Betz/GD SL 76605,2346 # 204900
To: Darrell Fetzer 76636,241 (X) Date: 25-Aug-92 22:47:17
Darrel, this method for calling the original int 8 handler at the proper rate
for any counter value was passed on to me by a friend in CLMFORUM, Rud
Merriam. Hope it's useful.
The easiest way to generate approximetly 18.2 pulses is to do the following:
1. Create a long counter variable.
2. At every tick of the 8254 add the count loaded into the 8254 to the
counter.
3. If the counter exceeds 65k then call the old interrupt vector.
4. Do a mod 65k on the counter to get the number of ticks over 65k.
That works for any value you put into the 8254 counter.
___________________ Subj: HTimer / High Resolution Timer ___________________
Fm: Mark Betz/GD SL 76605,2346 # 194299
To: Sarwan Narine 76675,164 Date: 01-Aug-92 14:39:41
Hi, Sarwan.
>> How do you program the timer
You do have a knack for these all-inclusive questions, don't you? <g>. Ok,
prepare for an exhaustive discussion of the PC timer...On your motherboard
there is either an 8254 timer chip, or it's equivalent embedded in a VLSI
chipset. This chip actually consists of 3 seperate timer registers, and a
status/input register. The three channels are 0, 1, and 2, and they are
addressed at ports 0x40, 0x41, and 0x42 respectively. Port 0x43 is the
status/input register, where you send commands or read the timer status. The
timer channels are actually 16-bit counter registers. Channel 0 is the system
timer tick, the one we're interested in. Channel 1 is the ram refresh pulse..
don't mess with it. Channel 2 is hardwired to the PC speaker. It can be used
to pump digitized sound to the speaker, but we're not getting into that
today. The way that the counters work is simple: they are loaded with a
value, and then they count down until the value reaches 0, at which time
something happens. In the case of channel 1 that something is a system timer
interrupt (int 08). The _rate_ at which the timers count down is 1.193180
megahertz. This means that the counter value is decremented 1,193,180 times
per second. The initial value loaded into the channel 1 register by the bios
at startup is 0, which is immediately decremented to yield 65535. If you
divide 1,193,180 / 65535 you get 18.2xxx, which is the approximate default
rate of the system timer. One other consideration is very important. The
timer channels operate in several modes. The default mode for channel 1 is
square wave mode. This means that the timer is decremented by _2_ on each
pulse, not 1. The first time it hits 0 it is reloaded, and the second time it
hits 0 the interrupt occurs.
Ok, there are some basic operations you need to do, in this order:
* get the current timer mode so you can save it
* reprogram the timer to pulse mode
* load the counter with the right value (roughly 239 for a
5000 hz tick)
* reprogram the timer to it's original values on exit
I was going to attempt to post all the code here, but it's too long. So I'm
going to email you one file, and suggest downloading two others. None of
these will show you _exactly_ how to do what you want, but, taken together
you should be able to figure it out. If you can't you shouldn't be messing
with the timer anyway <g>. The one I'll mail is TIMER.ZIP, which contains
some C code I threw together for the last guy who asked this question. The
other two are TRANS.ZIP and HELPPC.ZIP in LIB 11. TRANS is a hi-res timing
program I wrote for a compiled bimap competition we had some time ago.
--Mark
...........................................................................
Editor's Note : See the following files in GAMERS section 11
HTIMER.ZIP 33K 19-Nov-92 HTimer v1.2, hi-res timing class for BC++
HTMRMS.ZIP 33K 19-Nov-92 HTimer v1.2, hi-res timing class for MSC++ 7.x
...........................................................................
Fm: Bob Provencher/GD SL 71621,2632 # 245378
To: Mark Betz/Ass't SysOp 76605,2346 (X) Date: 15-Nov-92 20:27:49
Hi Mark,
That HTIMER class is pretty wild! You're doin some pretty neat stuff in
there. I didn't really expect to see anything and I didn't I did get some
other wierd results. If I do this (I tried the for loop)
while( !kbhit() )
{
for ( ; timer.getElapsed() < cycle; )
;
cout << timer.getElapsed() << timer.timerOff() << endl;
}
The result I get are
0 100120
And I still get some values below 100000. Is there anything about
getElapsed() that it can't be called twice in succession like that?
Bob
...........................................................................
Fm: Mark Betz/Ass't SysOp 76605,2346 # 245512
To: Bob Provencher/GD SL 71621,2632 (X) Date: 15-Nov-92 23:53:29
I kind of got a kick out of doing the interface between the class and the
hardware. That was the neatest part, for me. What I'd eventually like to do,
after I get the core calculation engine working in all cases, is to extend
the class so that you have a timer that returns mics, millis, hundredths,
tenths, and seconds, as well as specialty timers, like one that counts a
certain amount of time and then makes a callback to your app.
I've been able to show that the problem shows up in getElapsed and timerOff.
The error is taking place in calcElapsed. What happens is that it
occasionally returns too large a value. For example, in this code:
timer.timerOn();
while (time < 100000L) {
time = timer.getElapsed();
}
cout << timer.timerOff() << '\n';
what happens is that you usually exit the while() loop when time > 100000,
something like 100025. Occasionally getElapsed will return an absurd value,
like 142000, which bounces you out of the loop. Then timerOff() is called and
it gets the correct count again, which might be 65000, or whatever. This is
what was causing the original problem that Jesse saw. This only happens when
calcElapsed is dealing with a rollover in the counter register; i.e., when
ticks > 0. At the hardware level, the max mics you can count before a
register rollover takes place is 65535/1.193, or 54932. Since the register
can be anywhere in it's count when a timer starts up, this is often much less
for the first cycle. That's why I hooked int 8 and provided a rolling tick
counter. Somewhere in the interaction between ticks and register counts I'm
getting a bogus value.
...........................................................................
Fm: Mark Betz/Ass't SysOp 76605,2346 # 245566
To: Bob Provencher/GD SL 71621,2632 (X) Date: 16-Nov-92 01:58:55
I've got it!! I don't know how to fix it yet, but I found the problem. I owe
Jesse one for writing code that caused this problem to surface. It's an
extremely subtle bug that strikes pseudo-randomly (I say pseudo, because it's
dependent on the resonances between the speed of execution of a polling loop,
and the speed of the timer chip). Btw, I have to say, with reddened face,
that I damn well should have caught this myself. It'll show up in any long
series of short timing cycles. In longer timing cycles it will tend to get
buried.
This'll be tricky to explain, but I'll give it a shot, since I'd like some
ideas on how to prevent it. It's really a concurrency problem. The problem
arises out of the constraints of the timer chip counter registers. Imagine
the count value in the 16-bit counter along a time line:
count: 65535 32767 0
|____________________|_____________________|
| |
sys event: start cycle int 08
The timer causes an interrupt 8 at "terminal count" ( 0 ), then reloads the
counter register with 65535 (actually 0 in the default case, which is
immediately decremented to either 65535 in mode 3, or 65534 in mode 2, before
counting resumes) and starts over. The whole process is driven at 1.193180
Mhz, so that you have the counter register decremented 1,193,180 times per
second. The default frequency for the system timer interrupt, btw, is derived
from 1,193,180 hz./65535 = 18.2 hz., which is the number of times the process
reaches terminal count in one second.
You do hi-res timing on the PC by watching the counter register. Each
decrement in the register is equal to approximately 800 nanoseconds. You
latch the register count when you start the timer, and latch it again when
you finish timing. If you're lucky the timeline looks like this:
count: 65535 55432 32767 20013 0
|_____|______________|_______|_____________|
| |
timer: start finish
In this case the calculation is simple: microseconds = (start-finish)/1.193.
Where you run into trouble is when this happens:
count: 65535 55432 32767 20013 0
|_____|______________|_______|_____________|
| |
timer: finish start
In this case we grabbed the counter when it was at 20013. While we were
timing it wrapped around, and when we grabbed it again it was at 55432. The
calculation in this case is microseconds = start + (65535-finish)/1.193. One
more level of additional complexity exists when the timer wraps more than
once during a timing session (a "session" is defined as the time between
calls to either getElapsed or timerOff). In both of these cases you can see
that the only way for the program to know if the counter has wrapped is to
watch the timer interrupt. We _know_ that each timer interrupt signals that
the counter has wrapped.
Now that I've laid the foundation, here's the bug:
count: 65535 43345 32767 2 0
|___________|________|___________________|_|
| |
timer: start finish
The program latches the finishing counter value, 2 in this case. However,
before it can calculate the elapsed time the counter reaches terminal count
and issues an interrupt. Now the program thinks there was a wrap in the
counter, when there actually wasn't one during the timing interval: it
occured immediately after.
One possible solution is to "lock" the tick counter, so that it cannot be
modified by the interrupt handler while an elapsed time is being calculated.
The problem with this approach is that you can't sit in the interrupt handler
waiting for the app to finish so you can update the tick counter. The best
you can do is set a flag in the interrupt handler so that it will remember to
update the tick counter on the next pass. This isn't workable, since the next
opportunity to update the tick counter will be 54,945 microseconds away, and
that's unacceptable resolution for the timer class.
I apologize for the very long message, but it's a highly technical, and I
think very interesting problem. If anyone has any ideas I'd love to hear
them. I need to be able to let the tick counter run free, but somehow
guarantee that it is valid for a particular calculation of elapsed time.
Ahh, a possible solution just hit me. If the interrupt handler sees that an
elapsed time is being calculated, it sets a "waiting" flag, instead of
incrementing the tick counter, and then IRETS. In the HTimer::calcElapsed
function the code checks the waiting flag after it's done with it's
calculation, and updates the tick counter itself if the flag is set. What do
you think?
--Mark
...........................................................................
Fm: Mark Betz/Ass't SysOp 76605,2346 # 246084
To: Bob Provencher/GD SL 71621,2632 (X) Date: 16-Nov-92 22:58:23
Hi. That's a universal truth of bugs <g>. The reason why I didn't catch that
error was related to the test suite. The tests I created have one fatal flaw:
they're each performed once! When I saw the correct result I thought,
"Yippee, it works", and went on about my business. Now I find that there's an
error that occurs as rarely as once in one thousand cycles. Repetitive
testing would have caught it. I learned a lesson, and luckily I got to learn
it on a relatively obscure piece of PD code. If it had been something mission
critical I would have been burned, or someone else would have, perhaps
literally. I can take comfort in the fact that, in a mission-critical
project, someone else would have found it and fired me <g>.
Btw, I tried locking the tick counter, and it improved the situation, but the
problem didn't go away. I found that I could eliminate it for the end of the
timing cycle by moving some assignments so that they occured differently with
relation to the latching of the counter. I also found that the same problem
was occuring at the start of the timer cycle, in timerOn(). This has proven
tougher to deal with, but I think I have it subdued for the moment using a
test for the error condition (an approach I detest, but I'll use it until
something else works).
The whole thing makes a pretty interesting test case of a real-time
hardware/software interface problem. I'll upload the current version asap,
and perhaps we can bang on it enough to make it puke again. I'd like to get
to the point where HTimer is a 100% solid concurrent multiple-channel timing
object.
--Mark
...........................................................................